package com.yupi.springbootinit.manager.limiting;

import lombok.Data;
//import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

/**
 * Created With IntelliJ IDEA.
 * Descriptions:
 * author: Mr.Du
 * Date: 2023/7/5
 * Time: 16:46
 */
@Service
@ConfigurationProperties(prefix = "sliding.window.limiter")
@Data
public class SlidingWindowRateLimiter {
    
    /**
     * 单位时间
     */
    private long unitOfTime;
    
    private int subCycle;
    
    private int thresholdPerMin;
    
    private final TreeMap<Long, Integer> counters = new TreeMap<>();
    
    
    /**
     * 滑动窗口时间算法实现
     */
    boolean slidingWindowsTryAcquire(String userId) {
        // 将当前时间戳（以 UTC 时区为准）进行舍去操作，使其对齐到最近的子周期的起始时间
        // 例如，如果子周期长度为 10 秒，并且当前时间戳是 1632345678，那么上述表达式的结果将是 1632345670，即对齐到最近的以 0 结尾的子周期起始时间。
        long currentWindowTime = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC) / subCycle * subCycle; //获取当前时间在哪个小周期窗口
        int currentWindowNum = countCurrentWindow(currentWindowTime); //当前窗口总请求数
        
        //超过阀值限流
        if (currentWindowNum >= thresholdPerMin) {
            return false;
        }
        
        //计数器+1
        //int tmp = counters.get(currentWindowTime);
        //counters.put(currentWindowTime, tmp);
        counters.merge(currentWindowTime, 1, Integer::sum);
        return true;
    }
    
    /**
     * 统计当前窗口的请求数
     */
    private int countCurrentWindow(long currentWindowTime) {
        //计算窗口开始位置
        long startTime = currentWindowTime - subCycle * (unitOfTime / 1000l / subCycle - 1);
        int count = 0;
        
        //遍历存储的计数器
        Iterator<Map.Entry<Long, Integer>> iterator = counters.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Long, Integer> entry = iterator.next();
            // 删除无效过期的子窗口计数器
            if (entry.getKey() < startTime) {
                iterator.remove();
            } else {
                //累加当前窗口的所有计数器之和
                count = count + entry.getValue();
            }
        }
        return count;
    }
    
}
